package vsvm.math.statistics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

public class Characteristics {
	
		
	/**
	 *  Returns the largest value from sample data
	 *  
	 *  @param data sample data
	 *  @return largest value
	 */
	public static double getMax(double[] data)
	{
		double max = data[0];
		for (int i = 0; i < data.length; i++)
			max = data[i] > max ? data[i] : max;
		return max;
	}
	
	/**
	 * Returns the smallest value from sample data
	 * 
	 * @param data sample data
	 * @return smallest value
	 */
	public static double getMin(double[] data)
	{
		double min = data[0];
		for (int i = 0; i < data.length; i++)
			min = data[i] < min ? data[i] : min;
		return min;
	}
	
    //TODO getRange efektyviau kai reikia ir min ir max
	
	
	//TODO gal verta dalinti kas tarkim 1000 el.? del precisiono
	
	/**
	 * Returns the empirical mean value of given sample data
	 * 
	 * @param data sample data
	 * @return empirical mean
	 */
	public static double getMean(double[] data)
	{
		double sum = 0.0;
		int n = data.length;
		for (int i = 0; i < n; i++) sum += data[i];
		return sum/n;
	}
	
	//TODO gal verta dalinti kas tarkim 1000 el.? del precisiono
	/**
	 * Returns the empirical non-biased variance of given sample data
	 * 
	 * @param data sample data
	 * @return non-biased empirical variance
	 */
	public static double getVariance(double[] data)
	{
		double m = getMean(data);
		int    n = data.length;
		if (n <= 1) return 0; 
		
		double sum = 0.0;
		
		for (int i = 0; i < n; i++)
			sum += (data[i] - m)*(data[i] - m);
		
		return sum/(n - 1); //Pataisytoji dispersija			
	}
	
	/**
	 * Returns the empirical non-biased standard deviation
	 * of given sample data
	 * 
	 * @param data sample data
	 * @return non-biased standard deviation
	 */
	
	public static double getStDev(double[] data)
	{
		return Math.sqrt(getVariance(data));
	}
	
	
	//TODO exceptiona, kai daumenu maziau nei 3?
	/**
	 * Returns the empirical non-biased kurtosis
	 * of given sample data
	 * 
	 * @param data sample data
	 * @return non-biased kurtosis
	 */
	public static double getKurtosis(double[] data)
	{
		int n = data.length;
		double a, b;
		if (n <= 1) return 0.0;
		if (n <= 3){
			a = 1.0/n;
			b = 3;
		}
		else{
			a = (1.0*n/(n - 1))*(1.0*(n + 1)/(n - 2))/(n - 3);
			b = (3.0*(n - 1)/(n - 2))*(1.0*(n - 1)/(n - 3));			
		}
		
		double m 	 = getMean(data);
		double stDev = getStDev(data);		
		double sum   = 0.0;
		
		for (int i = 0; i < n; i++)
			sum += Math.pow(data[i] - m, 4)/stDev;
		
		return a*sum - b;		
	}
	
	//TODO exceptiona, kai daumenu maziau nei 3?
	/**
	 * Returns the empirical non-biased skewness
	 * of given sample data
	 * 
	 * @param data sample data
	 * @return non-biased skewness
	 */
	public static double getSkewness(double[] data)
	{
		long n = data.length;
		double a, b;
		if (n <= 1) return 0.0;
		if (n == 2){
			a = 0.5;			
		}
		else{
			a = (1.0*n/(n - 1))/(n - 2);						
		}
		
		double m 	 = getMean(data);
		double stDev = getStDev(data);		
		double sum   = 0.0;
		
		for (int i = 0; i < n; i++)
			sum += Math.pow(data[i] - m, 3)/stDev;
		
		return a*sum;	
	}	
	
	//TODO throw exception jeigu nelygus kiekiai
	public static double getPearsonCorrelation(double[] x, double[] y) {
		double res = 0.0;
			
		double mx = Characteristics.getMean(x);
		double my = Characteristics.getMean(y);
		
		double[] xy = new double[x.length];
		for (int i = 0; i < x.length; i++)
			xy[i] = (x[i] - mx)*(y[i] - my);		
		
		double mxy = Characteristics.getMean(xy);
		mxy = (mxy*x.length)/(x.length - 1);
		
		if (mxy == 0) return 0;
		return mxy/(Characteristics.getStDev(x)*Characteristics.getStDev(y));
	}
	
	/*public static double[] rank(double[] sample, int ranks) {		
		double limit = 3*Characteristics.getStDev(sample);
		double mean  =   Characteristics.getMean(sample); 
		double max = Double.NEGATIVE_INFINITY;
		double min = Double.POSITIVE_INFINITY;
		
		for (int i = 0; i < sample.length; i++) {
			if (sample[i] < min && sample[i] > mean - limit)
				min = sample[i];
			if (sample[i] > max && sample[i] < mean + limit)
				max = sample[i];
		}		
		
		double step = (max - min)/ranks;
		
		double[] rankings = new double[sample.length];
		double[] splits = new double[ranks];
		for (int i = 1; i <= ranks; i++)
			splits[i - 1] = min + i*step;
		
		splits[ranks - 1] = Double.POSITIVE_INFINITY;		
		
		for (int i = 0; i < sample.length; i++){
			int j = 1;
			while (sample[i] > splits[j - 1]) j++;
			rankings[i] = j;
		}
		
		return rankings;
	}*/
	
	private static double[] rankSorted(ArrayList <Pair> list) {
		PairComparator pc = new Characteristics().new PairComparator();
		Pair [] pairs = new Pair[list.size()];
		list.toArray(pairs);
		
		double[] ranks = new double[list.size()];		
		int crank = 1;		
		
		for (int i = 0; i < pairs.length; i++) {
			int j = i;
			int rankSum = crank;
			while(i + 1 < pairs.length && pc.compare(pairs[i], pairs[i + 1]) == 0) {
				rankSum += ++crank; i++;
			}
			
			double mrank = (1.0*rankSum)/(i - j + 1);
			for (int k = j; k <= i; k++)
				ranks[pairs[k].index] = mrank;
			
			crank++;
		}
		
		return ranks;
		
	}
	
	public static double getSpearmanCorrelation(double[] sample1, double[] sample2) {
		Characteristics c = new Characteristics();
		//TODO throw exception
		if (sample1.length != sample2.length) {
			System.out.println("Spearman Correlation Error: not same size samples");
			return 0;
		}
		ArrayList <Pair> list1 = new ArrayList();
		ArrayList <Pair> list2 = new ArrayList();
		
		for (int i = 0; i < sample1.length; i++){
			list1.add(c.new Pair(sample1[i], i));
			list2.add(c.new Pair(sample2[i], i));
		}
		
		Collections.sort(list1, c.new PairComparator());
		Collections.sort(list2, c.new PairComparator());
		
		double[] ranks1 = Characteristics.rankSorted(list1);
		double[] ranks2 = Characteristics.rankSorted(list2);	
		
		return Characteristics.getPearsonCorrelation(ranks1, ranks2);
	}
	
	public double getKendallCorrelation(double[] sample1, double[] sample2)
	{
		return 0;
	}
	
	public double getKrammerCorrelation(double[] sample1, double[] sample2) {
		return 0;
	}
	
	private class Pair {
		
		double value;
		int index;
		
		public Pair(double value, int index) {
			this.value = value; this.index = index;
		}
	}
	
	private class PairComparator implements Comparator <Pair> {
		public int compare(Pair p1, Pair p2){
			return  p1.value < p2.value ? -1 : (Math.abs(p1.value - p2.value) < 1e-8 ? 0 : 1);
		}
	}
}
